/****************************************************************************
 * Copyright (c) 2007 Composent, Inc. and others.
 *
 * This program and the accompanying materials are made
 * available under the terms of the Eclipse Public License 2.0
 * which is available at https://www.eclipse.org/legal/epl-2.0/
 *
 * Contributors:
 *    Composent, Inc. - initial API and implementation
 *
 * SPDX-License-Identifier: EPL-2.0
 *****************************************************************************/

package org.eclipse.ecf.internal.provider.filetransfer.scp;

import com.jcraft.jsch.*;
import java.io.*;
import java.net.URL;
import java.util.Map;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.ecf.core.security.*;
import org.eclipse.ecf.core.util.Proxy;
import org.eclipse.osgi.util.NLS;

/**
 *
 */
public class ScpUtil implements UserInfo, UIKeyboardInteractive {

	public static final String SCP_SSHHOMEDIRECTORY = System
			.getProperty(
					"org.eclipse.ecf.filetransfer.scp.util.sshHomeDirectory", "sshHomeDirectory"); //$NON-NLS-1$
	public static final String SCP_PUBLICKEYFILE = System.getProperty(
			"org.eclipse.ecf.filetransfer.scp.util.keyFile", "keyFile"); //$NON-NLS-1$
	public static final String SCP_KNOWNHOSTSFILE = System
			.getProperty(
					"org.eclipse.ecf.filetransfer.scp.util.knownHostsFile", "knownHostsFile"); //$NON-NLS-1$

	public static final int DEFAULT_SCP_PORT = Integer
			.parseInt(System.getProperty(
					"org.eclipse.ecf.filetransfer.scp.util.scpPort", "22"));

	private IScpFileTransfer handler;
	private String password;
	private String passphrase;
	private Session session;

	private String sshHome = null;
	private String keyFile = null;
	private String knownHostsFile = null;

	public ScpUtil(IScpFileTransfer handler) throws JSchException, IOException,
			UnsupportedCallbackException {
		this.handler = handler;
		final JSch jsch = new JSch();
		final URL url = handler.getTargetURL();
		int port = url.getPort();
		if (port == -1)
			port = DEFAULT_SCP_PORT;
		setupOptions(jsch);
		promptUsername();
		String username = handler.getUsername();
		if (username == null)
			throw new IOException(Messages.ScpUtil_EXCEPTION_USERNAME_NOT_NULL);
		session = jsch.getSession(handler.getUsername(), url.getHost(), port);
		setupProxy();
		session.setUserInfo(this);
	}

	Session getSession() {
		return session;
	}

	void promptUsername() throws IOException, UnsupportedCallbackException {
		final IConnectContext connectContext = handler.getConnectContext();
		if (connectContext != null) {
			final CallbackHandler callbackHandler = connectContext
					.getCallbackHandler();
			if (handler != null) {
				final Callback[] callbacks = new Callback[2];
				final NameCallback nc = new NameCallback(
						Messages.ScpOutgoingFileTransfer_USERNAME_PROMPT);
				String user = handler.getUsername();
				if (user != null)
					nc.setName(user);
				callbacks[0] = nc;
				callbacks[1] = new PasswordCallback(
						Messages.ScpOutgoingFileTransfer_PASSWORD_PROMPT);
				callbackHandler.handle(callbacks);
				handler.setUsername(nc.getName());
			}
		}
	}

	String promptCredentials(boolean usePassphrase) {
		try {
			final IConnectContext connectContext = handler.getConnectContext();
			if (connectContext != null) {
				final CallbackHandler callbackHandler = connectContext
						.getCallbackHandler();
				if (handler != null) {
					final Callback[] callbacks = new Callback[2];
					final NameCallback nc = new NameCallback(
							Messages.ScpOutgoingFileTransfer_USERNAME_PROMPT);
					String user = handler.getUsername();
					if (user != null)
						nc.setName(user);
					callbacks[0] = nc;
					if (usePassphrase) {
						callbacks[1] = new PassphraseCallback(
								Messages.ScpOutgoingFileTransfer_PASSPHRASE_PROMPT);
					} else
						callbacks[1] = new PasswordCallback(
								Messages.ScpOutgoingFileTransfer_PASSWORD_PROMPT);
					callbackHandler.handle(callbacks);
					handler.setUsername(nc.getName());
					if (usePassphrase) {
						passphrase = ((PassphraseCallback) callbacks[1])
								.getPassphrase();
					} else
						password = ((PasswordCallback) callbacks[1])
								.getPassword();
				}
			}
			return (usePassphrase) ? this.passphrase : this.password;
		} catch (final Exception e) {
			return null;
		}
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.jcraft.jsch.UserInfo#getPassphrase()
	 */
	public String getPassphrase() {
		return promptCredentials(true);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.jcraft.jsch.UserInfo#getPassword()
	 */
	public String getPassword() {
		return promptCredentials(false);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.jcraft.jsch.UserInfo#promptPassphrase(java.lang.String)
	 */
	public boolean promptPassphrase(String message) {
		return (keyFile != null);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.jcraft.jsch.UserInfo#promptPassword(java.lang.String)
	 */
	public boolean promptPassword(String message) {
		return true;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.jcraft.jsch.UserInfo#promptYesNo(java.lang.String)
	 */
	public boolean promptYesNo(String message) {
		return true;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see com.jcraft.jsch.UserInfo#showMessage(java.lang.String)
	 */
	public void showMessage(String message) {
		// do nothing
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.jcraft.jsch.UIKeyboardInteractive#promptKeyboardInteractive(java.
	 * lang.String, java.lang.String, java.lang.String, java.lang.String[],
	 * boolean[])
	 */
	public String[] promptKeyboardInteractive(String destination, String name,
			String instruction, String[] prompt, boolean[] echo) {
		promptCredentials(false);
		return new String[] { password };
	}

	/**
	 */
	void setupProxy() {
		com.jcraft.jsch.Proxy jProxy = null;
		final Proxy proxy = handler.getProxy();
		if (proxy != null) {
			final String hostname = proxy.getAddress().getHostName();
			final int port = proxy.getAddress().getPort();
			if (proxy.getType().equals(Proxy.Type.HTTP)) {
				if (port == -1)
					jProxy = new ProxyHTTP(hostname);
				else
					jProxy = new ProxyHTTP(hostname, port);
			} else if (proxy.getType().equals(Proxy.Type.SOCKS)) {
				if (port == -1)
					jProxy = new ProxySOCKS5(hostname);
				else
					jProxy = new ProxySOCKS5(hostname, port);
			}
			if (jProxy != null)
				session.setProxy(jProxy);
		}
	}

	private void setupOptions(JSch jsch) {
		// Get sshHome
		sshHome = getProperty(SCP_SSHHOMEDIRECTORY, sshHome);
		if (sshHome == null) {
			final String userHome = System.getProperty("user.home"); //$NON-NLS-1$
			if (userHome != null) {
				File dir = new File(userHome + File.separator + ".ssh"); //$NON-NLS-1$
				if (dir.exists())
					sshHome = dir.getAbsolutePath();
				else
					dir = new File(userHome + File.separator + "ssh"); //$NON-NLS-1$
				if (dir.exists())
					sshHome = dir.getAbsolutePath();
			}
		}
		keyFile = getProperty(SCP_PUBLICKEYFILE);
		if (keyFile != null) {
			if (!(new File(keyFile).exists()))
				keyFile = null;
		} else {
			File file = new File(sshHome + File.separator + "id_dsa"); //$NON-NLS-1$
			if (file.exists())
				keyFile = file.getAbsolutePath();
			else {
				file = new File(sshHome + File.separator + "id_rsa"); //$NON-NLS-1$
				if (file.exists())
					keyFile = file.getAbsolutePath();
			}
		}
		if (keyFile != null) {
			try {
				jsch.addIdentity(keyFile);
			} catch (final JSchException e) {
				Activator
						.getDefault()
						.log(new Status(
								IStatus.ERROR,
								Activator.PLUGIN_ID,
								IStatus.ERROR,
								Messages.ScpOutgoingFileTransfer_EXCEPTION_SETTING_SSH_IDENTITY,
								e));
			}
		}
		knownHostsFile = getProperty(SCP_KNOWNHOSTSFILE);
		if (knownHostsFile != null) {
			if (!(new File(knownHostsFile).exists()))
				knownHostsFile = null;
		} else {
			final File file = new File(sshHome + File.separator + "known_hosts"); //$NON-NLS-1$
			if (!file.exists()) {
				knownHostsFile = null;
			} else {
				knownHostsFile = file.getAbsolutePath();
			}
		}
		if (knownHostsFile != null) {
			try {
				jsch.setKnownHosts(knownHostsFile);
			} catch (final JSchException e) {
				Activator
						.getDefault()
						.log(new Status(
								IStatus.ERROR,
								Activator.PLUGIN_ID,
								IStatus.ERROR,
								Messages.ScpOutgoingFileTransfer_EXCEPTION_SETTING_KNOWN_HOSTS,
								e));
			}
		}
	}

	private String getProperty(String key, String def) {
		final Map options = handler.getOptions();
		if (options == null)
			return def;
		final String result = (String) options.get(key);
		if (result == null)
			return def;
		return result;
	}

	private String getProperty(String key) {
		return getProperty(key, null);
	}

	String trimTargetFile(String string) {
		return string;
	}

	void checkAck(InputStream ins) throws IOException {
		checkAck(ins.read(), ins);
	}

	void checkAck(int b, InputStream ins) throws IOException {
		if (b == 0)
			return;
		if (b == -1)
			throw new IOException(Messages.ScpUtil_EXCEPTION_UNKNOWN_SCP_ERROR);

		if (b == 1 || b == 2) {
			final StringBuffer sb = new StringBuffer();
			int c;
			do {
				c = ins.read();
				sb.append((char) c);
			} while (c != '\n');
			if (b == 1) { // error
				throw new IOException(NLS.bind(Messages.ScpUtil_SCP_ERROR,
						sb.toString()));
			}
			if (b == 2) { // fatal error
				throw new IOException(NLS.bind(Messages.ScpUtil_SCP_ERROR,
						sb.toString()));
			}
		}

	}

	void sendZeroToStream(OutputStream outs) throws IOException {
		// send '\0'
		final byte[] buf = new byte[1];
		buf[0] = 0;
		outs.write(buf, 0, 1);
		outs.flush();
	}

	void dispose() {
		if (session != null) {
			session.disconnect();
			session = null;
		}
		handler = null;
		password = null;
		passphrase = null;
		sshHome = null;
		keyFile = null;
		knownHostsFile = null;
	}
}
